package org.gikolet.ch2.android.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.text.DynamicLayout;
import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.Layout.Alignment;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

public class DynamicTextView extends View {
	private DynamicLayout layout;
	private TextPaint paint;

	public DynamicTextView(Context context) {
		this(context, null);
	}

	public DynamicTextView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public DynamicTextView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		this.paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
		this.paint.setTextSize(16f);
		this.paint.linkColor = Color.BLUE;
		this.layout = new DynamicLayout(new SpannableStringBuilder(), paint, 0,
				Alignment.ALIGN_NORMAL, 1, 0, true);
	}

	private static Object FROM_BELOW = new NoCopySpan.Concrete();

	Path mHighlightPath = new Path();
	Paint mHighlightPaint = new Paint();
	boolean mHighlightPathBogus = true;
	int mHighlightColor = Color.GREEN;

	@Override
	protected void onDraw(Canvas canvas) {
		Layout layout = this.layout;
		if (layout != null) {
			int selStart = -1;
			int selEnd = -1;

			Path highlight = null;

			if (isFocused() || isPressed()) {
				selStart = Selection.getSelectionStart(this.layout.getText());
				selEnd = Selection.getSelectionEnd(this.layout.getText());

				if (selStart >= 0 && isEnabled()) {

					if (mHighlightPathBogus) {
						mHighlightPath.reset();
						layout.getSelectionPath(selStart, selEnd,
								mHighlightPath);
						// mHighlightPathBogus = false;
					}

					// XXX should pass to skin instead of drawing directly
					mHighlightPaint.setColor(mHighlightColor);
					mHighlightPaint.setStyle(Paint.Style.FILL);

					highlight = mHighlightPath;
				}
			}
			layout.draw(canvas, highlight, mHighlightPaint, 0);
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		CharSequence text = this.layout.getText();

		if (text instanceof Spannable) {
			Spannable stext = (Spannable) text;

			switch (keyCode) {
			case KeyEvent.KEYCODE_DPAD_UP:
			case KeyEvent.KEYCODE_DPAD_LEFT:
				if (action(UP, stext)) {
					invalidate();
					return true;
				}
				break;

			case KeyEvent.KEYCODE_DPAD_DOWN:
			case KeyEvent.KEYCODE_DPAD_RIGHT:
				if (action(DOWN, stext)) {
					invalidate();
					return true;
				}
				break;

			case KeyEvent.KEYCODE_DPAD_CENTER:
			case KeyEvent.KEYCODE_ENTER:
				if (event.getRepeatCount() == 0) {
					if (action(CLICK, stext)) {
						invalidate();
						return true;
					}
				}
				break;
			}
		}
		return false;
	}

	@Override
	protected void onFocusChanged(boolean gainFocus, int direction,
			Rect previouslyFocusedRect) {

		CharSequence text = this.layout.getText();

		if (text instanceof Spannable) {
			Spannable s = (Spannable) text;
			Selection.removeSelection(s);

			if ((direction & View.FOCUS_BACKWARD) != 0) {
				s.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);

			} else {
				s.removeSpan(FROM_BELOW);
			}
		}
	}

	public SpannableStringBuilder getText() {
		return (SpannableStringBuilder)this.layout.getText();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int action = event.getAction();

		if (action == MotionEvent.ACTION_UP
				|| action == MotionEvent.ACTION_DOWN) {
			int x = (int) event.getX();
			int y = (int) event.getY();

			x += getScrollX();
			y += getScrollY();

			CharSequence text = this.layout.getText();

			if (text instanceof Spannable) {
				Spannable buffer = (Spannable) text;
				int line = layout.getLineForVertical(y);
				int off = layout.getOffsetForHorizontal(line, x);

				ClickableSpan[] link = buffer.getSpans(off, off,
						ClickableSpan.class);

				if (link.length != 0) {
					if (action == MotionEvent.ACTION_UP) {
						link[0].onClick(this);
					} else if (action == MotionEvent.ACTION_DOWN) {
						Selection.setSelection(buffer, buffer
								.getSpanStart(link[0]), buffer
								.getSpanEnd(link[0]));
					}

					return true;

				} else {
					Selection.removeSelection(buffer);
				}
			}
		}

		return super.onTouchEvent(event);
	}

	private static final int CLICK = 1;
	private static final int UP = 2;
	private static final int DOWN = 3;

	// UIスレッドからのみ使うこと
	private Rect tmpRect = new Rect();

	private boolean action(int what, Spannable buffer) {
		Layout layout = this.layout;

		getLocalVisibleRect(this.tmpRect);

		// int areatop = widget.getScrollY();
		// int areabot = areatop + widget.getHeight();

		// とりあずパディングは全く考えない
		int areatop = this.tmpRect.top;
		int areabot = areatop + this.tmpRect.height();

		int linetop = layout.getLineForVertical(areatop);
		int linebot = layout.getLineForVertical(areabot);

		int first = layout.getLineStart(linetop);
		int last = layout.getLineEnd(linebot);

		ClickableSpan[] candidates = buffer.getSpans(first, last,
				ClickableSpan.class);

		int a = Selection.getSelectionStart(buffer);
		int b = Selection.getSelectionEnd(buffer);

		int selStart = Math.min(a, b);
		int selEnd = Math.max(a, b);

		if (selStart < 0) {
			if (buffer.getSpanStart(FROM_BELOW) >= 0) {
				selStart = selEnd = buffer.length();
			}
		}

		if (selStart > last)
			selStart = selEnd = Integer.MAX_VALUE;
		if (selEnd < first)
			selStart = selEnd = -1;

		switch (what) {
		case CLICK:
			if (selStart == selEnd) {
				return false;
			}

			ClickableSpan[] link = buffer.getSpans(selStart, selEnd,
					ClickableSpan.class);

			if (link.length != 1)
				return false;

			link[0].onClick(this);
			break;

		case UP:
			int beststart,
			bestend;

			beststart = -1;
			bestend = -1;

			for (int i = 0; i < candidates.length; i++) {
				int end = buffer.getSpanEnd(candidates[i]);

				if (end < selEnd || selStart == selEnd) {
					if (end > bestend) {
						beststart = buffer.getSpanStart(candidates[i]);
						bestend = end;
					}
				}
			}

			if (beststart >= 0) {
				Selection.setSelection(buffer, bestend, beststart);
				return true;
			}

			break;

		case DOWN:
			beststart = Integer.MAX_VALUE;
			bestend = Integer.MAX_VALUE;

			for (int i = 0; i < candidates.length; i++) {
				int start = buffer.getSpanStart(candidates[i]);

				if (start > selStart || selStart == selEnd) {
					if (start < beststart) {
						beststart = start;
						bestend = buffer.getSpanEnd(candidates[i]);
					}
				}
			}

			if (bestend < Integer.MAX_VALUE) {
				Selection.setSelection(buffer, beststart, bestend);
				return true;
			}

			break;
		}

		return false;
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		int contentWidth = w - (getPaddingLeft() + getPaddingRight());

		if (this.layout.getWidth() != contentWidth) {
			this.layout.increaseWidthTo(contentWidth);
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int contentWidth = MeasureSpec.getSize(widthMeasureSpec)
				- (getPaddingLeft() + getPaddingRight());

		if (this.layout.getWidth() != contentWidth) {
			this.layout.increaseWidthTo(contentWidth);
		}

		setMeasuredDimension(this.layout.getWidth(), this.layout.getHeight());
	}
}
